/*-----------------------------------------------------------------------------
	6502 Macroassembler and Simulator

Copyright (C) 1995-2003 Michal Kowalski

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-----------------------------------------------------------------------------*/

// ZeroPageView.cpp : implementation file
//

#include "stdafx.h"
#include "resource.h"
#include "ZeroPageView.h"
#include "MemoryGoto.h"
#include "MemoryDC.h"
#include "simOSI.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


CFont CZeroPageView::m_Font;
LOGFONT CZeroPageView::m_LogFont;
COLORREF CZeroPageView::m_rgbTextColor;
COLORREF CZeroPageView::m_rgbBkgndColor;

/////////////////////////////////////////////////////////////////////////////
// CZeroPageView

IMPLEMENT_DYNCREATE(CZeroPageView, CView)

CZeroPageView::CZeroPageView()
{}

CZeroPageView::~CZeroPageView()
{}


BEGIN_MESSAGE_MAP(CZeroPageView, CView)
  //{{AFX_MSG_MAP(CZeroPageView)
  ON_WM_VSCROLL()
  ON_WM_MOUSEWHEEL()
  ON_WM_KEYDOWN()
  ON_WM_SIZE()
  ON_WM_ERASEBKGND()
  ON_WM_CONTEXTMENU()
  ON_UPDATE_COMMAND_UI(ID_MEMORY_GOTO, OnUpdateMemoryGoto)
  ON_COMMAND(ID_MEMORY_GOTO, OnMemoryGoto)
  ON_UPDATE_COMMAND_UI(ID_MEMORY_CHG, OnUpdateMemoryChg)
  ON_COMMAND(ID_MEMORY_CHG, OnMemoryChg)
  //}}AFX_MSG_MAP
  ON_MESSAGE(CBroadcast::WM_USER_MEM_CHANGED, OnMemChanged)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CZeroPageView drawing

void CZeroPageView::OnDraw(CDC* pDC)
{
  CMemoryDoc *pDoc= (CMemoryDoc *)GetDocument();
  ASSERT(pDoc->IsKindOf(RUNTIME_CLASS(CMemoryDoc)));
  CString line(_T(' '), 1 + max(m_nCx, 8));
  UINT16 addr= pDoc->m_uAddress;
  TCHAR hex[8];
  int lim= bytes_in_line();	// ilość wyświetlanych w jednym wierszu bajtów
  COutputMem& mem= *pDoc->m_pMem;
  CMemoryDC dcMem(*pDC, this, m_rgbBkgndColor);
  if (dcMem.m_hDC == 0)
	  return;

  dcMem.SelectObject(&m_Font);
  dcMem.SetTextColor(m_rgbTextColor);
  dcMem.SetBkColor(m_rgbBkgndColor);
  if (!m_OSIFont)
  {
	if (!m_hOSITTF)
		m_hOSITTF = LoadFont(IDR_OSIFONT, _T("OSI") );
	if ( m_hOSITTF || IsFontAvailable(_T("OSI"), NULL) )
	{
		m_OSIFont = CreateFont (
			m_LogFont.lfHeight,                 // nHeight
			0,                         // nWidth
			0,                         // nEscapement
			0,                         // nOrientation
			FW_DONTCARE,                 // nWeight
			FALSE,                     // bItalic
			FALSE,                     // bUnderline
			0,                         // cStrikeOut
			DEFAULT_CHARSET,              // nCharSet
			OUT_DEFAULT_PRECIS,        // nOutPrecision
			CLIP_CHARACTER_PRECIS,       // nClipPrecision
			NONANTIALIASED_QUALITY,           // nQuality
			FIXED_PITCH | FF_DONTCARE | TMPF_TRUETYPE,  // nPitchAndFamily
			_T("OSI"));            // lpszFacename
	}
  }


  for (int i=0, y=0; i <= m_nCy; i++, y += m_nChrH)
  {
    int nLo= mem[addr];
    int nHi= mem[(addr + 1) & 0xFF];
    int nPtr= nLo + (nHi << 8);
    ASSERT(nPtr >= 0 && nPtr <= 0xFFFF);
    line.Format(_T("%02X  %02X '%c'  %04X "), int(addr), nLo, nLo > 0 ? nLo : ' ', nPtr);
    
    line.Format(_T("%02X  %02X "), int(addr), nLo );
    CSize sz = pDC->GetTextExtent(line); 
    dcMem.TextOut(1, y, line);    
	HFONT hOldFont = (HFONT) ::SelectObject(dcMem.m_hDC, m_OSIFont);
	{
		TEXTMETRIC tm;
		pDC->GetTextMetrics(&tm);
		int cx = tm.tmAveCharWidth;			
		WCHAR wChar = ((unsigned char) nLo) ;
		//OSI font swapped } & |
		//if (gCharSet != 1)
		{
			if (wChar == 0x7c)
				wChar = 0x7D;
			else if (wChar == 0x7D)
				wChar = 0x7C;
		}
		wChar |= 0xF000;

		::TextOutW(dcMem.m_hDC, sz.cx, y , &wChar, 1);
		sz.cx += cx*2;
	}
	::SelectObject(dcMem.m_hDC, hOldFont);    
    line.Format(_T(" %04X"), nPtr);
    
    int j;
    for (j= 0; j < lim; j++)
    {
      wsprintf(hex, _T(" %02X"), int(mem[(nPtr + j) & 0xFFFF]));
      line += hex;
    }

    addr++;
    dcMem.TextOut(sz.cx, y, line);
    if (addr >= 0x100)	// doszliśmy do końca strony zerowej?
      break;
  }

  dcMem.BitBlt();
}

/////////////////////////////////////////////////////////////////////////////
// CZeroPageView diagnostics

#ifdef _DEBUG
void CZeroPageView::AssertValid() const
{
  CView::AssertValid();
}

void CZeroPageView::Dump(CDumpContext& dc) const
{
  CView::Dump(dc);
}
#endif //_DEBUG


/////////////////////////////////////////////////////////////////////////////
// obliczenia pomocnicze

void CZeroPageView::calc(CDC *pDC)
{
  RECT rect;
  GetClientRect(&rect);

  pDC->SelectObject(&m_Font);
  TEXTMETRIC tm;
  pDC->GetTextMetrics(&tm);
  m_nChrH = (int)tm.tmHeight + (int)tm.tmExternalLeading;
  m_nChrW = tm.tmAveCharWidth;

  m_nCx = (rect.right-1) / m_nChrW;	// ilość kolumn
  m_nCy = rect.bottom / m_nChrH;	// ilość wierszy
//  if (rect.bottom % m_nCharH)	// na dole wystaje kawałek wiersza?
  if (m_nCy == 0)
    m_nCy++;
}

//-----------------------------------------------------------------------------

void CZeroPageView::scroll(UINT nSBCode, int nPos, int nRepeat)
{
  CMemoryDoc *pDoc = (CMemoryDoc *)GetDocument();
  if (pDoc == NULL)
    return;
  switch (nSBCode)
  {
    case SB_ENDSCROLL:	// End scroll
      break;

    case SB_LINEDOWN:	// Scroll one line down
      switch (find_next_addr(pDoc->m_uAddress,*pDoc->m_pMem))
      {
        case 0:
	  break;	// dalej już się nie da
	case 1:
	  RECT rect;
	  get_view_rect(rect);
	  UpdateWindow();	// dla uniknięcia problemów z odświeżaniem
	  ScrollWindow(0,-m_nChrH,&rect,&rect);
	  UpdateWindow();
	  break;
	case 999999:
	  InvalidateRect(NULL);	// przerysowanie całego okna
	  break;
      }
      break;

    case SB_LINEUP:	// Scroll one line up
      switch (find_prev_addr(pDoc->m_uAddress,*pDoc->m_pMem))
      {
        case 0:
	  break;	// jesteśmy już na początku
	case 1:
	  RECT rect;
	  get_view_rect(rect);
	  UpdateWindow();	// dla uniknięcia problemów z odświeżaniem
          ScrollWindow(0,m_nChrH,&rect,&rect);
	  UpdateWindow();
	  break;
	case 999999:
	  InvalidateRect(NULL);	// przerysowanie całego okna
	  break;
	default:
	  ASSERT(FALSE);
	  break;
      }
      break;

    case SB_PAGEDOWN:	// Scroll one page down
    {
      RECT rect;
      get_view_rect(rect);
      switch (find_next_addr(pDoc->m_uAddress,*pDoc->m_pMem,m_nCy))
      {
        case 0:
	  break;	// dalej już się nie da
	default:
	  InvalidateRect(NULL);	// przerysowanie całego okna
	  break;
      }
      break;
    }

    case SB_PAGEUP:	// Scroll one page up
    {
      RECT rect;
      get_view_rect(rect);
      switch (find_prev_addr(pDoc->m_uAddress,*pDoc->m_pMem,m_nCy))
      {
        case 0:
	  break;	// jesteśmy już na początku
	default:
	  InvalidateRect(NULL);	// przerysowanie całego okna - zmieniło się kilka rozkazów
	  break;
      }
      break;
    }

    case SB_TOP:	// Scroll to top
    {
      RECT rect;
      get_view_rect(rect);
      int lines= find_delta(pDoc->m_uAddress,0,*pDoc->m_pMem,m_nCy);
      if (lines == 999999)
	InvalidateRect(NULL);	// przerysowanie całego okna
      else if (lines > 0)
      {
	if (lines >= m_nCy)
	  InvalidateRect(NULL);	// przerysowanie całego okna
	else
          ScrollWindow(0,lines*m_nChrH,&rect,&rect);
      }
      break;
    }

    case SB_BOTTOM:	// Scroll to bottom
    {
      RECT rect;
      get_view_rect(rect);
      int lines= find_delta(pDoc->m_uAddress,0xFFFF,*pDoc->m_pMem,m_nCy);
      if (lines == 999999)
	InvalidateRect(NULL);	// przerysowanie całego okna
      else if (lines < 0)
      {
	if (-lines >= m_nCy)
	  InvalidateRect(NULL);	// przerysowanie całego okna
	else
          ScrollWindow(0,lines*m_nChrH,&rect,&rect);
      }
      break;
    }

    case SB_THUMBPOSITION:   // Scroll to the absolute position. The current position is provided in nPos
      break;
    case SB_THUMBTRACK:	// Drag scroll box to specified position. The current position is provided in nPos
    {
      RECT rect;
      get_view_rect(rect);
      UINT16 pos= (nPos); //+0x8000);
//      pos -= pos % bytes_in_line();
      int lines= find_delta(pDoc->m_uAddress,pos,*pDoc->m_pMem,m_nCy);
      if (lines == 999999)
	InvalidateRect(NULL);	// przerysowanie całego okna
      else if (lines)
      {
	if (abs(lines) >= m_nCy)
	  InvalidateRect(NULL);	// przerysowanie całego okna
	else
          ScrollWindow(0,lines*m_nChrH,&rect,&rect);
      }
      break;
    }

    case 0x100+SB_LINERIGHT:
    {
      switch (find_next_addr(pDoc->m_uAddress,*pDoc->m_pMem,1,1))
      {
        case 0:			// już nie ma przesunięcia
	  break;
	default:
	  InvalidateRect(NULL);	// przerysowanie całego okna
	  break;
      }
      break;
    }
    case 0x100+SB_LINELEFT:
    {
      switch (find_prev_addr(pDoc->m_uAddress,*pDoc->m_pMem,1,1))
      {
        case 0:			// już nie ma przesunięcia
	  break;
	default:
	  InvalidateRect(NULL);	// przerysowanie całego okna
	  break;
      }
      break;
    }
    default:
      break;
  }
//  set_scroll_range();
  if (nSBCode != SB_ENDSCROLL)
    SetScrollPos(SB_VERT,((int)pDoc->m_uAddress /*- 0x8000*/) /* / bytes_in_line() */ );
}

/////////////////////////////////////////////////////////////////////////////
// CZeroPageView message handlers

void CZeroPageView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) 
{
  if (pInfo)
    return;
  pDC->SetBkMode(OPAQUE);
  pDC->SelectObject(&m_Font);
  calc(pDC);

  CView::OnPrepareDC(pDC, pInfo);
}


BOOL CZeroPageView::PreCreateWindow(CREATESTRUCT& cs) 
{
  cs.style |= /*WS_CLIPSIBLINGS |*/ WS_VSCROLL;

  return CView::PreCreateWindow(cs);
}


void CZeroPageView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
  scroll(nSBCode,nPos);

  // CView::OnVScroll(nSBCode, nPos, pScrollBar);
}

BOOL CZeroPageView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) // 1.3.2 added mouse scroll wheel support
{
	if (zDelta > 0)
		scroll(SB_LINEUP,0,0);
	else	
		scroll(SB_LINEDOWN,0,0);
  return true; 	
}

void CZeroPageView::OnInitialUpdate()
{
	CView::OnInitialUpdate();

	CClientDC dc(this);
	calc(&dc);
	set_scroll_range();
}

//-----------------------------------------------------------------------------

int CZeroPageView::set_scroll_range()
{
  CMemoryDoc *pDoc = (CMemoryDoc *)GetDocument();
  if (!pDoc)
    return -1;

  int ret= 0;
  int scr= m_nCy; // * bytes_in_line();
  if (scr >= 0x100)
  {
    SetScrollRange(SB_VERT, 0, 0);	// całość mieści się w oknie
    return -1;
  }
  int rng= 0x100;
  SCROLLINFO si;
  si.cbSize = sizeof si;
  si.fMask = SIF_RANGE | SIF_PAGE;
  si.nMin = 0;
  si.nMax = rng;
  si.nPage = scr;
  SetScrollInfo(SB_VERT, &si, FALSE);
  int nPos= pDoc->m_uAddress;
  if (nPos > rng - scr)
      nPos = rng - scr;
  scroll(SB_THUMBTRACK, nPos);
  return ret;
}

//-----------------------------------------------------------------------------

// odszukanie adresu wiersza pamięci poprzedzającego dany wiersz
int CZeroPageView::find_prev_addr(UINT32 &addr, const COutputMem &mem, int cnt/*= 1*/, int bytes/*= 0*/)
{
  ASSERT(cnt > 0);
  if (cnt < 0)
    return 0;

  if (!bytes)
    bytes = 1; //bytes_in_line();
  int pos= addr - cnt * bytes;
  ASSERT(pos <= 0xFFFF);
  if (pos < 0)
  {
    if (addr % bytes)
      return addr=0, 999999;	// trzeba przerysować całe okno
    cnt = addr / bytes;
    addr = (UINT16)0;
  }
  else
    addr = (UINT16)pos;
  return cnt;			// o tyle wierszy można przesunąć
}


// odszukanie adresu wiersza pamięci następującego po danym wierszu
int CZeroPageView::find_next_addr(UINT32 &addr, const COutputMem &mem,
				int cnt/*= 1*/, int bytes/*= 0*/)
{
  ASSERT(cnt > 0);
  if (cnt < 0)
    return 0;

  if (!bytes)
    bytes = 1; //bytes_in_line();		// ilość bajtów w wierszu
  int scr= bytes * m_nCy;		// i na całym oknie
  int pos= addr + cnt * bytes;
  if (pos+scr > 0x100)
  {
    pos = 0x100 - scr;
    if (pos < 0)
      pos = 0;
    cnt = pos - addr;
    if (cnt % bytes)
      cnt = 999999;			// trzeba przerysować całe okno
    else
      cnt /= bytes;
  }
  addr = (UINT16)pos;
  return cnt;
}


// spr. o ile wierszy należy przesunąć zawartość okna aby dotrzeć od 'addr' do 'dest'
int CZeroPageView::find_delta(UINT32 &addr, UINT16 dest, const COutputMem &mem, int max_lines)
{
  if (dest == addr)
    return 0;

  int bytes= 1; //bytes_in_line();
  if (dest < addr)
  {
    int lines= (addr - dest);
    addr = dest;
    if (lines % bytes)
      return 999999;
    return lines / bytes;
  }
  else
  {
    int scr= bytes * m_nCy;
    if (scr >= 0x100)
      if (addr)
      {
	addr = 0;
	return 999999;
      }
      else
	return 0;
    if (dest > 0x100-scr)
      dest = 0x100 - scr;
    int lines= (dest - addr);
    addr = dest;
    if (lines == 0)
      return 0;
    if (lines % bytes)
      return 999999;
    return -(lines / bytes);
  }
}

//-----------------------------------------------------------------------------

void CZeroPageView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  switch (nChar)
  {
    case VK_DOWN:
      scroll(SB_LINEDOWN, 0, nRepCnt);
      break;
    case VK_UP:
      scroll(SB_LINEUP, 0, nRepCnt);
      break;
    case VK_NEXT:
      scroll(SB_PAGEDOWN, 0, nRepCnt);
      break;
    case VK_PRIOR:
      scroll(SB_PAGEUP, 0, nRepCnt);
      break;
    case VK_HOME:
      scroll(SB_TOP, 0, nRepCnt);
      break;
    case VK_END:
      scroll(SB_BOTTOM, 0, nRepCnt);
      break;
    case VK_LEFT:
      scroll(0x100+SB_LINELEFT, 0, nRepCnt);
      break;
    case VK_RIGHT:
      scroll(0x100+SB_LINERIGHT, 0, nRepCnt);
      break;
    default:
      break;
//      CView::OnKeyDown(nChar, nRepCnt, nFlags);
  }
}


void CZeroPageView::OnSize(UINT nType, int cx, int cy)
{
	CView::OnSize(nType, cx, cy);

	CClientDC dc(this);
	calc(&dc);
	set_scroll_range();
}


BOOL CZeroPageView::OnEraseBkgnd(CDC* pDC)
{
//  CRect rect;
//  GetClientRect(rect);
//  pDC->FillSolidRect(rect, m_rgbBkgndColor);
  return TRUE;
//  return CView::OnEraseBkgnd(pDC);
}


void CZeroPageView::OnContextMenu(CWnd* pWnd, CPoint point)
{
  CMenu menu;
  if (!menu.LoadMenu(IDR_POPUP_ZPMEMORY))
    return;
  CMenu *pPopup = menu.GetSubMenu(0);
  ASSERT(pPopup != NULL);

  if (point.x == -1 && point.y == -1)     // menu wywołane przy pomocy klawiatury?
  {
    CRect rect;
    GetClientRect(rect);

    point = rect.TopLeft();
    CPoint ptTopLeft(0, 0);
    ClientToScreen(&ptTopLeft);
    point.x = ptTopLeft.x + rect.Width() / 2;   // ustawiamy się na środku okna
    point.y = ptTopLeft.y + rect.Height() / 2;
  }

  pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, AfxGetMainWnd());
}

//-----------------------------------------------------------------------------

void CZeroPageView::OnUpdateMemoryGoto(CCmdUI* pCmdUI)
{
  pCmdUI->Enable(true);
}

void CZeroPageView::OnMemoryGoto() 
{
  static UINT addr= 0;
  CMemoryGoto dlg;
  dlg.m_uAddr = addr;

  if (dlg.DoModal() == IDOK)
  {
    addr = dlg.m_uAddr;
    scroll(SB_THUMBTRACK, dlg.m_uAddr/*-0x8000*/, 1);
  }
}


void CZeroPageView::OnUpdateMemoryChg(CCmdUI* pCmdUI)
{
  pCmdUI->Enable(true);
}

#include "MemoryChg.h"

void CZeroPageView::OnMemoryChg()
{
  CMemoryDoc *pDoc= (CMemoryDoc *)GetDocument();
  ASSERT(pDoc->IsKindOf(RUNTIME_CLASS(CMemoryDoc)));

  CMemoryChg dlg(*pDoc->m_pMem, this);
  dlg.DoModal();
  InvalidateRect(NULL);
}


void CZeroPageView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
	if (lHint == 'show')
	{
		CClientDC dc(this);
		calc(&dc);
		set_scroll_range();
	}
}

LRESULT CZeroPageView::OnMemChanged(WPARAM wParam, LPARAM lParam)
{
	if (wParam >= 0 && wParam < 0x100)
		Invalidate();
	return 0;
}